﻿using System;
using System.ComponentModel;
using System.Reactive;
using System.Reactive.Linq;
using System.Reactive.Subjects;

namespace Rxx.Labs.Reactive
{
	[DisplayName("Operator Overloads")]
	[Description("Reactive AsOperational extensions.")]
	public sealed class OperationalLab : BaseConsoleLab
	{
		protected override void Main()
		{
			RunExperiments();
		}

		[Description("Operator overload methods are combinators.  Rxx provides an AsOperational extension method that "
							 + "converts sequences of the primitive numeric types into sequences that can be combined using the "
							 + "basic C# operators.  Currently included are the binary +, -, *, / operators and the unary +, - "
							 + "operators, for all of the primitive numeric types in which C# implicitly defines them.")]
		public void BasicExperiment()
		{
			var xs = new Subject<int>();
			var ys = new Subject<int>();
			var zs = new Subject<int>();

			IObservable<int> query = xs.AsOperational() + ys - zs;

			using (query.Subscribe(ConsoleOutput))
			{
				xs.OnNext(1);
				ys.OnNext(5);
				zs.OnNext(2);

				xs.OnNext(2);
				ys.OnNext(6);
				zs.OnNext(3);

				xs.OnNext(3);
				xs.OnNext(4);

				ys.OnNext(7);
				ys.OnNext(8);

				zs.OnNext(4);
				zs.OnNext(5);

				WaitForKey();
			}
		}

		[Description("The AsOperational extension method allows you to define binary and unary operator overloads as "
							 + "functions for any type, not just the primitive numeric types.  You can also specify the binary "
							 + "strategy for combining sequences operationally; although, this experiment does not show that "
							 + "feature in particular.")]
		public void AdvancedExperiment()
		{
			var xs = new Subject<int>();
			var ys = new Subject<int>();
			var zs = new Subject<int>();

			// Define an operational factory so that operators are only specified once.
			var o = (Func<IObservable<int>, OperationalObservable<int>>)
				(source => source.AsOperational(
					add: (left, right) => left + (right * 5),
					subtract: (left, right) => left - right,
					multiply: (left, right) => left * right,
					divide: (left, right) => (left * 2) / right,
					negative: value => -value));

			IObservable<int> query = (-o(xs) * 2) + ys - (o(zs) / 4);

			using (query.Subscribe(ConsoleOutput))
			{
				xs.OnNext(1);
				ys.OnNext(5);
				zs.OnNext(4);

				xs.OnNext(2);
				ys.OnNext(6);
				zs.OnNext(8);

				xs.OnNext(3);
				ys.OnNext(7);
				zs.OnNext(12);

				WaitForKey();
			}
		}
	}
}